#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <tool/ystr.h>

#ifdef  UNIT_TEXT_SIZE
#undef  UNIT_TEXT_SIZE
#endif
#define UNIT_TEXT_SIZE  10000

/** String function data struct */
struct strFnData {
  unsigned char sizeof_char;
};

/*======= declare internal function */
boolean ystr_freeFn(strFn *fnP);
/* specific operation */
int ystr_memcmpIgnoreCase(const char *str1, const char *str2, size_t n);
void *ystr_memrchr(const void *str, int c, size_t n);
boolean ystr_isNumbers(const char *str);
boolean ystr_isNumbersWithLen(const char *str, int strLen);
char *ystr_cat(const strFn fn, const char *str1, const char *str2);
char *ystr_catWithLen(const strFn fn, const char *str1, const char *str2, 
    unsigned int newStrLen);
char *ystr_clone(const strFn fn, const char *str);
char *ystr_cloneLen(const strFn fn, const char *str, 
    const size_t strLen);
char *ystr_cloneES(const strFn fn, char *str);
char *ystr_cloneESLen(const strFn fn, char *str, 
    const size_t strLen);
char *ystr_resize(const strFn fn, char *ptr);
char *ystr_resizeLen(const strFn fn, char *ptr, const unsigned int n);
char *ystr_trimLeft(char *ptr, const unsigned int n);
char *ystr_removeWhiteSpace(const strFn fn, const char *str);
char *ystr_removeWhiteSpaceNPos(const strFn fn, const char *str, 
    const int posStart, const int posEnd);

/*======= directly exposed function */
/** Create a <code>strFn</code> that provide ystr function pointers.
@return a working struct of ystr function pointers
  or <code>NULL</code> if failure.
*/
strFn ystr_createFn(void) {
  strFn fn;
  FN_MALLOC_ERRMSG(fn, sizeof(struct strFn), 
      "Error: ystr_createFn(void) malloc(fn)")
  if (fn == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(fn->d, sizeof(struct strFnData), 
      "Error: ystr_createFn(void) malloc(fn->d)")
  if (fn->d == NULL) {ystr_freeFn(&fn); return NULL;}
  /*--- data ---*/
  fn->d->sizeof_char = sizeof(char);
  /*--- init function pointers ---*/
  fn->freeFn = ystr_freeFn;
  /* specific operation */
  fn->memcmpIgnoreCase = ystr_memcmpIgnoreCase;
  fn->memrchr = ystr_memrchr;
  fn->isNumbers = ystr_isNumbers;
  fn->isNumbersWithLen = ystr_isNumbersWithLen;
  fn->cat = ystr_cat;
  fn->catWithLen = ystr_catWithLen;
  fn->clone = ystr_clone;
  fn->cloneLen = ystr_cloneLen;
  fn->cloneES = ystr_cloneES;
  fn->cloneESLen = ystr_cloneESLen;
  fn->resize = ystr_resize;
  fn->resizeLen = ystr_resizeLen;
  fn->trimLeft = ystr_trimLeft;
  fn->removeWhiteSpace = ystr_removeWhiteSpace;
  fn->removeWhiteSpaceNPos = ystr_removeWhiteSpaceNPos;
  return fn;
}

/*======= exposed through function pointers */
/** Free a <code>strFn</code>, free up the memory.
@param fnP pass in the address of <code>strFn</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean ystr_freeFn(strFn *fnP) {
  /* NOT critical if strFn is NULL, assume all's fine */
  if (*fnP == NULL) {return true;}
  if ((*fnP)->d != NULL) {
    /* free strFnData */
    free((*fnP)->d);
    (*fnP)->d = NULL;
  }
  /* free strFn */
  free(*fnP);
  *fnP = NULL;
  return true;
}

/** Compares the first <code>n</code> bytes of 
<code>str1</code> and <code>str2</code>. 
Does not stop comparing even after the null character 
(it always checks <code>n</code> characters).
Slower in c version.
@param str1 the first string.
@param str2 the second string.
@param n the number of characters to compare or 
  the length of the string to compare.
@return Returns zero if the first <code>n</code> bytes of 
  <code>str1</code> and <code>str2</code> are equal. 
  Returns less than zero or greater than zero 
  if <code>str1</code> is less than or greater than 
  <code>str2</code> respectively.
*/
int ystr_memcmpIgnoreCase(const char *str1, const char *str2, size_t n) {
  unsigned int diff;
  while (n > 0) {
    n--;
    diff = tolower((unsigned char) str1[n]) - 
		tolower((unsigned char) str2[n]);
    if (diff != 0) {
      return diff;  /* they are different */
    }
  }
  return 0; /* same */
}

/** Searches for the last occurrence of the character <code>c</code> 
(an unsigned char) to <code>n</code> bytes of characters. 
Starting from pointer <code>ptrEnd</code>
moving in reverse right to left. Slower in c version.
<p>WARNING: Thus the behaviour of this function is NOT the same as
ANSI C library <code>strrchr, i.e.
char *strrchr(const char *str, int c); </code>
</p>
<pre>ANSI C provides memchr, which scans memory using ascending      
addresses (to the right), starting from the specified           
address.  However, there's no equivalent reverse scan
(right to left) function.
</pre>
@param ptrEnd pointer to the end of the character array.
@param c the character to find.
@param n the number of characters to compare or 
  the length of the character array to compare, 
  including char at <code>ptrEnd</code>.
@return Returns a pointer pointing to the last matching character, 
  or null if no match was found.
*/
void *ystr_memrchr(const void *str, int c, size_t n) {
  unsigned char *ptr = (unsigned char *) str;
  while (n > 0) {
    if (*ptr == (unsigned char) c) {
      return (void *) ptr;
    }
    ptr--;
    n--;
  }
  return NULL;
}

/** Test if string is numbers.
@param str the string.
@return true or false.
*/
boolean ystr_isNumbers(const char *str) {
  if (str == NULL) {return false;}
  unsigned int strLen = strlen(str);
  if (strLen == 0) {return false;}
  do {
    strLen--;
    /* digit (0 to 9)? */
    if (!isdigit((unsigned char) str[strLen])) {return false;}
  } while (strLen > 0);
  return true;
}

/** Test if string is numbers, 
provide the exact length of string to test.
@param str the string.
@param strLen the length of string to test.
@return true or false.
*/
boolean ystr_isNumbersWithLen(const char *str, int strLen) {
  if (str == NULL || strLen == 0) {return false;}
  do {
    strLen--;
    /* digit (0 to 9)? */
    if (!isdigit((unsigned char) str[strLen])) {return false;}
  } while (strLen > 0);
  return true;
}

/** Return concaternation of two string.
Concaternate <code>str2</code> to the end of <code>str1</code>, 
not including <code>str1</code>'s <code>NULL</code> char.
The new string with a proper <code>NULL</code> end char is returned.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>strFn</code> to use.
@param str1 the first string.
@param str2 the second string.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_cat(const strFn fn, const char *str1, const char *str2) {
  if (fn == NULL || fn->d == NULL) {return NULL;}
  unsigned strLen = strlen(str1) + strlen(str2);
  char *newStr;
  /* +1 include the end of string NULL char */
  FN_MALLOC_ERRMSG(newStr, (strLen +1) * fn->d->sizeof_char, 
      "Error: ystr_cat(const strFn, const char *, const char *) malloc(newStr)")
  if (newStr == NULL) {return NULL;}
  /* WARNING: do NOT do following
  sprintf(newStr, "%s%s%c", str1, str2, '\0')
  it seem in some case sprintf(..) will fail! */
  if (sprintf(newStr, "%s%s", str1, str2) == -1) {
    return NULL;
  }
  /* NOT necessary:
  newStr[strLen] = NULL; */
  return newStr;
}

/** Return concaternation of two string and its' new length.
Concaternate <code>str2</code> to the end of <code>str1</code>, 
not including <code>str1</code>'s <code>NULL</code> char.
The new string with a proper <code>NULL</code> end char is returned and 
its length is given in <code>newStrLen</code>.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>strFn</code> to use.
@param str1 the first string.
@param str2 the second string.
@param newStrLen length of the new string.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_catWithLen(const strFn fn, const char *str1, const char *str2, 
    unsigned int newStrLen) {
  if (newStrLen <= 0) {return NULL;}
  /* assume: fn == NULL & ... is tested in ystr_cat(..)
    add or release memory, will return NULL if unsuccesful 
    +1 include the end of string NULL char */
  return realloc(ystr_cat(fn, str1, str2), (newStrLen +1) * fn->d->sizeof_char);
}

/** Duplicate a string.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>strFn</code> to use.
@param str the string.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_clone(const strFn fn, const char *str) {
  if (fn == NULL || fn->d == NULL || str == NULL) {return NULL;}
  unsigned int strLen = strlen(str);
  char *newStr;
  /* +1 include the end of string NULL char */
  FN_MALLOC_ERRMSG(newStr, (strLen +1) * fn->d->sizeof_char, 
      "Error: ystr_clone(const strFn, const char *) malloc(newStr)")
  if (newStr == NULL) {return NULL;}
  return strcpy(newStr, str);
  /* Which is faster? strcpy(str1, str2) or below?
  unsigned int i;
  for (i = 0; i < strLen; i++) {
    newStr[i] = str[i];
  }
  newStr[i] = '\0';
  return newStr;
  */
}

/** Duplicate a string with this length.
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>strFn</code> to use.
@param str the string.
@param strLen the length of the string to copy.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_cloneLen(const strFn fn, const char *str, 
    const size_t strLen) {
  if (fn == NULL || fn->d == NULL || str == NULL || strLen < 0) {
    return NULL;
  }
  char *newStr;
  /* +1 include the end of string NULL char */
  FN_MALLOC_ERRMSG(newStr, (strLen +1) * fn->d->sizeof_char, 
      "Error: ystr_cloneLen(const strFn, const char *, const size_t) malloc(newStr)")
  if (newStr == NULL) {return NULL;}
  memcpy(newStr, str, strLen);
  newStr[strLen] = '\0';
  return newStr;
}

/** Duplicate a string and replace any string 
that has a valid escape sequence chars.
"\ddd" octal digits and "\xdd" hexadecimal digit is not handled.
e.g. 2 chars "\n" in a string becomes 1 char '\n'
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>strFn</code> to use.
@param str the string.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_cloneES(const strFn fn, char *str) {
  if (str == NULL) {return NULL;}
  return ystr_cloneESLen(fn, str, strlen(str));
}

/** Duplicate a string with this length and replace any string 
that has a valid escape sequence chars.
"\ddd" octal digits and "\xdd" hexadecimal digit is not handled.
e.g. 2 chars "\n" in a string becomes 1 char '\n'
<p>Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
</p>
@param fn the <code>strFn</code> to use.
@param str the string.
@param strLen the length of the string to copy.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_cloneESLen(const strFn fn, char *str, 
    const size_t strLen) {
  if (fn == NULL || fn->d == NULL || str == NULL || strLen <= 0) {
    return NULL;
  }
  char *newStr;
  /* +1 include the end of string NULL char */
  FN_MALLOC_ERRMSG(newStr, (strLen +1) * fn->d->sizeof_char, 
      "Error: ystr_clone(const strFn, const char *) malloc(newStr)")
  if (newStr == NULL) {return NULL;}
  
  /* prepare for escape sequence search */
  boolean isEscapeSequence;
  char *nStr, *sStr, *sStrEnd, *pos, rChar;
  unsigned int tmpLen, nStrLen = 0;
  nStr = newStr;
  sStr = str;
  sStrEnd = str + strLen -1; /* end address of search */
  /* search for '\\' 
    pos < sStrEnd BUT NOT pos == sStrEnd 
    since we'll be testing the (pos +1) position char */
  while ((pos = strchr(sStr, '\\')) != NULL && pos < sStrEnd) {
    switch (*(pos +1)) {
    case 'n':
    /* \n 	New Line 	Moves the cursor to the 
      first position of the next line. */
      rChar = '\n';
      isEscapeSequence = true;
      break;
    case 'r':
    /* \r 	Carriage Return 	Moves the cursor to the 
      first position of the current line. */
      rChar = '\r';
      isEscapeSequence = true;
      break;
    case 't':
    /* \t 	Horizontal Tab 	Moves the cursor to the 
      next horizontal tabular position. */
      rChar = '\t';
      isEscapeSequence = true;
      break;
    case '0':
    /* \0 		Produces a null character. */
      rChar = '\0';
      isEscapeSequence = true;
      break;
    case '\\':
    /* \\ 		Produces a single backslash. */
      rChar = '\\';
      isEscapeSequence = true;
      break;
    case '"':
    /* \" 		Produces a double quote. */
      rChar = '\"';
      isEscapeSequence = true;
      break;
    case '\'':
    /* \' 		Produces a single quote. */
      rChar = '\'';
      isEscapeSequence = true;
      break;
    case '?':
    /* \? 		Produces a question mark. */
      rChar = '\?';
      isEscapeSequence = true;
      break;
    case 'x':
    /* \xdd  	 	Defines one character by the hexadecimal digit 
      (base-16 number).*/
      isEscapeSequence = false;
      break;
    case 'f':
    /* \f 	Form Feed 	Moves the cursor to the 
      first position of the next page. */
      rChar = '\f';
      isEscapeSequence = true;
      break;
    case 'b':
    /* \b 	Backspace 	Moves the cursor back 
      one position (non-destructive). */
      rChar = '\b';
      isEscapeSequence = true;
      break;
    case 'v':
    /* \v 	Vertical Tab 	Moves the cursor to the 
      next vertical tabular position. */
      rChar = '\v';
      isEscapeSequence = true;
      break;
    case 'a':
    /* \a  	Alert  	Produces an audible or visible alert. */
      rChar = '\a';
      isEscapeSequence = true;
      break;
    default:
    /* \ddd  	 	Defines one character by the octal digits (base-8 number). 
      Multiple characters may be defined in the same escape sequence, 
      but the value is implementation-specific (see examples). */
      isEscapeSequence = false;
      break;
    }
    if (isEscapeSequence == true) {
      /* copy NOT including the char '\\' at pos */
      tmpLen = pos - sStr;
      if (tmpLen > 0) {
        memcpy(nStr, sStr, tmpLen);
      }
      /* length copied & +1 char rChar */
      nStrLen += tmpLen +1;
      /* add the escape sequence */
      nStr[tmpLen] = rChar;
      /* next new string or copy target start position */
      nStr = &nStr[tmpLen +1];
      /* next start original string copy position will be:
        +2 skip pass 2 chars, '\\' at pos and the "rChar" pointer char position. */
      sStr = pos +2;
    } else {
      sStr = pos +1; /* search start from next char */
    }
    if (sStr >= sStrEnd) {
      break;
    }
  }
  if (sStr <= sStrEnd) {
    /* copy rest of string 
      including "sStr" char pointer position */
    tmpLen = strLen - (sStr - str);
    /* +1 include end of string NULL char of "str" */
    memcpy(nStr, sStr, tmpLen +1);
    /* add to total length 
      NB: length NOT including the end of string NULL char */
    nStrLen += tmpLen;
  } else { /* include end of string NULL char */
    newStr[nStrLen] = '\0';
  }
  if (nStrLen < strLen) {
    /* release left over memory */
    nStr = realloc(newStr, (nStrLen +1) * fn->d->sizeof_char);
    if (nStr != NULL) { /* successful ONLY */
      newStr = nStr;
    }
  }
  return newStr;
}

/** Attempt to resize (realloc) the character string, thus freeing some memory.
@param fn the <code>strFn</code> to use.
@param ptr the character array.
@return the resized character array OR the original array if failure occurs.
*/
char *ystr_resize(const strFn fn, char *ptr) {
  if (ptr == NULL) {return NULL;}
  /* release left over memory, +1 for end of string NULL */
  char *newStr = realloc(ptr, (strlen(ptr) +1) * fn->d->sizeof_char);
  if (newStr != NULL) { /* successful ONLY */
    ptr = newStr;
  }
  return ptr;
}

/** Attempt to resize (realloc) the character string, thus freeing some memory.
@param fn the <code>strFn</code> to use.
@param ptr the character array.
@param n the length of the array to be resize to.
@return the resized character array OR the original array if failure occurs.
*/
char *ystr_resizeLen(const strFn fn, char *ptr, const unsigned int n) {
  if (ptr == NULL) {return NULL;}
  /* release left over memory, +1 for end of string NULL */
  char *newStr = realloc(ptr, (n +1) * fn->d->sizeof_char);
  if (newStr != NULL) { /* successful ONLY */
    newStr[n] = '\0';
    ptr = newStr;
  }
  return ptr;
}

/** Return the pointer to the left position in the 
character array that is not a space or horizontal tab.
@param ptr the character array.
@param n the length of the array to be searched.
@return a pointer to the left position in the 
  character array that is not a space or horizontal tab.
*/
char *ystr_trimLeft(char *ptr, const unsigned int n) {
  char *ptrChk = ptr;
  unsigned int i = 0;
  while (i < n && (ptrChk[i] == ' ' || ptrChk[i] == '\t')) {
    i++;
  }
  return ptrChk;
}

/** Remove white space in a string and return a new string.
Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
@param fn the <code>strFn</code> to use.
@param str the string.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_removeWhiteSpace(const strFn fn, const char *str) {
  if (fn == NULL || fn->d == NULL || str == NULL) {return NULL;}
  unsigned int strLen = strlen(str);
  if (strLen == 0) {return NULL;}
  char *strNew;
  unsigned int i, len, strNewN;

  strNewN = UNIT_TEXT_SIZE;
  strNew = malloc(strNewN * fn->d->sizeof_char);
  i = 0;
  len = 0;
  while (i < strLen) {
    if (isspace((unsigned char) str[i])) {i++; continue;}
    /* not white space */
    strNew[len] = str[i];
    len++;
    i++;
    /* need more memory? +1 for required NULL */
    if ((len + 1) >= strNewN) {
      /* allocate more memory */
      strNewN += UNIT_TEXT_SIZE;
      char *strTmp = realloc(strNew, strNewN * fn->d->sizeof_char);
      /* unsuccessful */
      if (strTmp == NULL) {return NULL;}
      strNew = strTmp;
    }
  }
  /* memory managed test not required: if (len <= strLen) {} */
  strNew[len] = '\0';
  return realloc(strNew, (len + 1) * fn->d->sizeof_char);
}

/** Remove white space in a string beginning & 
ending with these position and return a new string.
Since this string is <code>malloc(..)</code>, to avoid memory leak
it should be freed when no longer needed with a call to 
<code>free(void *)</code>.
@param fn the <code>strFn</code> to use.
@param str the string.
@param charStart start position of str.
@param charEnd end position of str.
@return new string or <code>NULL</code> if unsuccessful.
*/
char *ystr_removeWhiteSpaceNPos(const strFn fn, const char *str, 
    const int posStart, const int posEnd) {
  if (fn == NULL || fn->d == NULL || str == NULL || posStart > posEnd) {
    return NULL;
  }
  char *strNew;
  unsigned int i, len, strNewN;
  
  strNewN = UNIT_TEXT_SIZE;
  strNew = malloc(strNewN * fn->d->sizeof_char);
  i = posStart;
  len = 0;
  while (i < posEnd) {
    if (isspace((unsigned char) str[i])) {i++; continue;}
    /* not white space */
    strNew[len] = str[i];
    len++;
    i++;
    /* need more memory? +1 for required NULL */
    if ((len + 1) >= strNewN) {
      /* allocate more memory */
      strNewN += UNIT_TEXT_SIZE;
      char *strTmp = realloc(strNew, strNewN * fn->d->sizeof_char);
      /* unsuccessful */
      if (strTmp == NULL) {return NULL;}
      strNew = strTmp;
    }
  }
  /* memory managed test not required: 
    if (len <= (posEnd - posStart)) {} */
  strNew[len] = '\0';
  return realloc(strNew, (len + 1) * fn->d->sizeof_char);
}
